package com.ihr360.payroll.service.salarytasklet.step.processor;

import com.ihr360.alps.api.ServiceProvider;
import com.ihr360.attendance.AttendancePeriodReport;
import com.ihr360.common.DateRange;
import com.ihr360.commons.data.util.Pair;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.model.meta.table.DynamicCell;
import com.ihr360.commons.model.type.FieldSource;
import com.ihr360.commons.model.type.TaxRole;
import com.ihr360.commons.model.type.WorkHourType;
import com.ihr360.expr.engine.DependencyEngine;
import com.ihr360.job.core.annotation.BeforeProcess;
import com.ihr360.job.core.entity.StepExecutionLog;
import com.ihr360.job.core.item.ItemProcessor;
import com.ihr360.job.core.logger.CalcLog;
import com.ihr360.job.core.repository.JobRepository;
import com.ihr360.job.core.scope.context.SimpleStepContext;
import com.ihr360.payperiod.PayPeriodTerm;
import com.ihr360.payroll.calc.StaffSalaryCalcPeriod;
import com.ihr360.payroll.calc.StaffSalaryCalcPeriodTerm;
import com.ihr360.payroll.context.SimpleStaffPayrollInfo;
import com.ihr360.payroll.dao.StaffPayrollInfoRepository;
import com.ihr360.payroll.model.dto.attendance.report.AttendanceMonthlyReport;
import com.ihr360.payroll.model.dto.reference.ReferenceService;
import com.ihr360.payroll.model.dto.salary.basecode.TaxDivision;
import com.ihr360.payroll.model.dto.salary.embeddable.RefServiceStatistics;
import com.ihr360.payroll.model.dto.salary.embeddable.StaffInfoSnapshot;
import com.ihr360.payroll.model.dto.salary.meta.SalaryField;
import com.ihr360.payroll.model.dto.salary.task.period.StaffPayPeriodTermCalcContext;
import com.ihr360.payroll.model.dto.salary.task.period.StaffPayPeriodTermSalary;
import com.ihr360.payroll.model.dto.salary.task.period.embeddable.StaffSalaryCalcContext;
import com.ihr360.payroll.model.dto.salary.task.period.embeddable.StaffSalarySnapshot;
import com.ihr360.payroll.model.dto.salary.task.period.embeddable.StaffSalaryTermId;
import com.ihr360.payroll.model.dto.staff.StaffPayrollInfo;
import com.ihr360.payroll.service.attendancereport.AttendanceBillService;
import com.ihr360.payroll.service.attendancereport.AttendanceMonthlyReportService;
import com.ihr360.payroll.service.payperiod.StaffPayPeriodTermSalaryService;
import com.ihr360.payroll.service.salarytasklet.SalaryCalcNamingUtils;
import com.ihr360.payroll.service.salarytasklet.SalaryReferenceDataService;
import com.ihr360.payroll.service.salarytasklet.convertor.MonthlyReportToPeriodReportConvertor;
import com.ihr360.payroll.service.salarytasklet.expr.engine.SalaryDependencyEngine;
import com.ihr360.payroll.service.salarytasklet.model.PreSalaryCalcProcessedResult;
import com.ihr360.payroll.service.salarytasklet.step.SalaryCalcContext;
import com.ihr360.payroll.service.salarytasklet.step.calculator.PersonalCalcLog;
import com.ihr360.payroll.service.salarytasklet.step.calculator.SalaryContextSalaryTermPayConvertor;
import com.ihr360.payroll.type.StaffRefStatus;
import com.ihr360.shared.basicstaffinfo.dao.SyncedStaffInfoRepository;
import com.ihr360.shared.basicstaffinfo.dto.SyncedBasicStaffInfo;
import com.ihr360.validation.ValidationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author davidwei
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PreSalaryCalcProcessor implements ItemProcessor<Map<String, StaffSalaryCalcPeriod>, PreSalaryCalcProcessedResult> {
    final Logger logger = LoggerFactory.getLogger(DependencyEngine.class.getName());
    private SimpleStepContext simpleStepContext;

    @Autowired
    private SalaryReferenceDataService salaryReferenceDataService;

    @Autowired
    private StaffPayPeriodTermSalaryService staffPayPeriodTermSalaryService;

    @Autowired
    private AttendanceMonthlyReportService attendanceMonthlyReportService;

    @Autowired
    private AttendanceBillService attendanceBillService;

    @Autowired
    private StaffPayrollInfoRepository staffPayrollInfoRepository;


    @Autowired
    private SyncedStaffInfoRepository syncedStaffInfoRepository;

    @Autowired
    private MonthlyReportToPeriodReportConvertor reportConvertor;

    @Autowired
    private JobRepository jobRepository;

    @Autowired
    private SalaryContextSalaryTermPayConvertor salaryTermPayConvertor;

    private SalaryCalcContext salaryCalcContext;

    private Set<String> inputSalaryFieldSet;

    private DateRange payrollPeriodRange;

    private DateRange attendancePeriodRange;

    private DateRange unionRange;

    private Map<String, Set<String>> salaryReferences;

    private Map<String, String> refCodeAndNameMap;

    private Set<String> termRefFields;

    private Map<StaffInfoSnapshot, PersonalCalcLog> logMap = new HashMap<>();

    private RefServiceStatistics refServiceStatistics;

    public void setSalaryCalcContext(SalaryCalcContext salaryCalcContext) {
        this.salaryCalcContext = salaryCalcContext;
        ValidationUtils.rejectIfEmpty(salaryCalcContext, "[薪资数据获取]", "计算上下文变量为空");
    }

    public void setPeriodRanges(DateRange payrollPeriodRange, DateRange attendancePeriodRange) {
        this.payrollPeriodRange = payrollPeriodRange;
        this.attendancePeriodRange = attendancePeriodRange;
        Date startdate = this.payrollPeriodRange.getStart().before(this.attendancePeriodRange.getStart()) ? this.payrollPeriodRange.getStart() : this.attendancePeriodRange.getStart();
        Date endData = this.payrollPeriodRange.getEnd().after(this.attendancePeriodRange.getEnd()) ? this.payrollPeriodRange.getEnd() : this.attendancePeriodRange.getEnd();
        this.unionRange = DateRange.build(startdate, endData);
    }

    @Override
    public PreSalaryCalcProcessedResult process(Map<String, StaffSalaryCalcPeriod> staffCalcPeriodMap) throws Exception {
        StopWatch clock = new StopWatch();
        clock.start("薪资计算 procsss");
        SalaryDependencyEngine dependencyEngine = salaryCalcContext.dependencyEngineHolder.getWrittenItem();

        refServiceStatistics = salaryCalcContext.getRefServiceStatistics();
        Map<String, SalaryField> fieldMap = salaryCalcContext.salaryFieldsHolder.getWrittenItem();
        if (CollectionUtils.isNotEmpty(fieldMap)) {
            inputSalaryFieldSet = fieldMap.values().stream().filter(s -> FieldSource.INPUT.equals(s.getSource())).map(s -> s.getCode()).collect(Collectors.toSet());
        }

        //获取员工纳税地区，纳税类型和工种
        String companyId = salaryCalcContext.getCompanyId();
        ValidationUtils.rejectIfEmpty(companyId, null, "无法获取公司标识");

        Set<String> staffIds = staffCalcPeriodMap.keySet();
        List<SyncedBasicStaffInfo> syncedBasicStaffInfos = syncedStaffInfoRepository.findByIdIn(staffIds);
        List<StaffPayrollInfo> staffPayrollInfos = staffPayrollInfoRepository.findStaffPayrollinfo(companyId, staffIds);
        int staffCnt = staffIds.size();
        Map<String, StaffPayrollInfo> staffPayrollInfoMap = null;
        if (staffPayrollInfos == null) {
            staffPayrollInfoMap = new HashMap<>(staffCnt);
        } else {
            staffPayrollInfoMap = staffPayrollInfos.stream().collect(Collectors.toMap(s -> s.getStaff().getId(), s -> s));
        }

        Map<String, SyncedBasicStaffInfo> basicStaffInfoMap = syncedBasicStaffInfos.stream().collect(Collectors.toMap(s -> s.getId(), s -> s));

        //拉取其他服务的数据
        this.salaryReferences = dependencyEngine.getSalaryReferences();

        List<ReferenceService> referenceServices = salaryCalcContext.refrenceServicesHolder.getWrittenItem();
        if (CollectionUtils.isNotEmpty(referenceServices)) {
            refCodeAndNameMap = referenceServices.stream().collect(Collectors.toMap(s -> s.getRefId(), s -> s.getRefName()));
        }

        //&& salaryCalcContext.hasSIHFFields
        if (salaryCalcContext.isReferenceBenefitData && refCodeAndNameMap.containsKey(ServiceProvider.SERVICE_ID.SIHF)) {
            refServiceStatistics.setRefSihf(true);
        } else {
            refServiceStatistics.setRefSihf(false);
        }

        if (refCodeAndNameMap.containsKey(ServiceProvider.SERVICE_ID.ATTENDANCE_MONTHLY_REPORT) && salaryCalcContext.hasAttendanceFields) {
            refServiceStatistics.setRefAttendance(true);
        } else {
            refServiceStatistics.setRefAttendance(false);
        }
        //获取输入数据
        Long termId = salaryCalcContext.getTermId();
        Map<String, StaffPayPeriodTermSalary> inputDataMap = staffPayPeriodTermSalaryService.loadPayperiodSalaryOfStaffsWithMapResult(termId, staffIds);

        //获取合并计税依赖数据
        this.termRefFields = dependencyEngine.getTermRefFields();
        Map<String, StaffSalarySnapshot> basedOnTermDataMap = null;
        PayPeriodTerm payPeriodTerm = salaryCalcContext.payPeriodTerm;
        if (payPeriodTerm != null) {
            if (payPeriodTerm.isMergeTaxTerm()) {
                Long basedOnTermId = payPeriodTerm.getBasedOnTermId();
                if (basedOnTermId != null) {
                    List<StaffPayPeriodTermSalary> salaries = staffPayPeriodTermSalaryService.loadPayPeriodSalaryOfStaffs(basedOnTermId, staffIds);
                    if (CollectionUtils.isNotEmpty(salaries)) {
                        basedOnTermDataMap = salaries.stream().collect(Collectors.toMap(s -> s.getStaffId(), s -> s.getSummarizedData()));
                    }
                }
            }
        }

        //获取考勤服务以外的数据
        Map<String, Map<String, Map<String, Object>>> staffReferencedDataMap = null;
        try {
            staffReferencedDataMap = salaryReferenceDataService.loadReferencedDataOfStaffs(salaryCalcContext.getCompanyId(), salaryReferences, staffIds, payrollPeriodRange, attendancePeriodRange);
        } catch (Ihr360Exception e) {
            jobRepository.error(simpleStepContext, "[薪资核算]", e.getMessage());
        }
        if (staffReferencedDataMap == null) {
            staffReferencedDataMap = new HashMap<>(1);
        }
        //获取考勤数据，考勤数据和薪资分段数据的结合决定了，薪资计算最终会被拆分为几段
        //TODO: 考勤的月份定义为起始月份
        int year = attendancePeriodRange.getFromYear();
        int month = attendancePeriodRange.getFromMonth();

        Map<String, AttendanceMonthlyReport> attendanceMonthlyReportMap = attendanceMonthlyReportService.getMonthlyReportOfStaffs(salaryCalcContext.getCompanyId(), salaryCalcContext.salaryPlanId, year, month, staffIds);

        boolean isPhasedCalcDisabled = salaryCalcContext.isPhasedCalcDisabled();

        //如果没有perid或者period里面没有term或者只要一个term返回true,否则表示有多个term，返回false
        Map<Boolean, List<StaffSalaryCalcPeriod>> singleOrMultiplePeriodMap = staffCalcPeriodMap.values().stream().collect(Collectors.groupingBy(s -> {
            if (isPhasedCalcDisabled) {
                return Boolean.TRUE;
            }
            if (s == null) {
                return Boolean.TRUE;
            }
            if (!s.hasPhase()) {
                return Boolean.TRUE;
            }
            if (s.isSinglePhase()) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        }));


        this.salaryTermPayConvertor.setPayRollPeriod(this.payrollPeriodRange);
        this.salaryTermPayConvertor.setAttendancePeriod(this.attendancePeriodRange);
        this.salaryTermPayConvertor.setSalaryFieldMap(this.salaryCalcContext.salaryFieldsHolder.getWrittenItem());
        this.salaryTermPayConvertor.setBasedOnTermFields(termRefFields);
        if (this.salaryCalcContext.payPeriodTerm != null) {
            this.salaryTermPayConvertor.setPlanName(SalaryCalcNamingUtils.toQuotedVariableName(this.salaryCalcContext.payPeriodTerm.getBasedOnPlanName()));
        }

        List<Pair<StaffPayPeriodTermCalcContext, StaffSalaryCalcPeriod>> result = new ArrayList<>(staffCalcPeriodMap.size());

        List<StaffSalaryCalcPeriod> staffs = singleOrMultiplePeriodMap.get(Boolean.TRUE);
        if (CollectionUtils.isNotEmpty(staffs)) {
            if (CollectionUtils.isNotEmpty(staffs)) {
                this.salaryTermPayConvertor.setIsSinglePhase(true);
                this.salaryTermPayConvertor.setIsSingleTerm(true);
                processStaffs(result, attendanceMonthlyReportMap, Collections.emptyMap(), inputDataMap, staffReferencedDataMap, staffs, staffPayrollInfoMap, basicStaffInfoMap, basedOnTermDataMap);
            }
        }
        staffs = singleOrMultiplePeriodMap.get(Boolean.FALSE);
        if (CollectionUtils.isNotEmpty(staffs)) {
            //这部分用户是分段用户,需要获取分段考勤数据
            Map<String, List<DateRange>> staffSalaryRanages = staffs.stream().collect(Collectors.toMap(s -> s.getStaffId(), s -> {
                List<StaffSalaryCalcPeriodTerm> terms = s.getPhases();
                if (CollectionUtils.isEmpty(terms)) {
                    return null;
                }
                List<DateRange> ranges = terms.stream().map(t -> {
                    Date endOn = t.getEndOn();
                    if (endOn == null) {
                        endOn = attendancePeriodRange.getEnd();
                    }
                    Date startOn = t.getStartOn();
                    if (startOn.before(attendancePeriodRange.getStart())) {
                        startOn = attendancePeriodRange.getStart();
                    }
                    return DateRange.build(startOn, endOn);
                }).collect(Collectors.toList());
                return ranges;
            }));

            //考勤数据的获取应该以考勤周期为准 eric 2018/5/24增加传入salaryPlanId
            Map<String, List<AttendancePeriodReport>> staffAttendancePeriodReport = attendanceBillService.getPeriodReportOfStaffs(companyId, salaryCalcContext.salaryPlanId, staffSalaryRanages, attendancePeriodRange, year, month);
            this.salaryTermPayConvertor.setIsSinglePhase(false);
            this.salaryTermPayConvertor.setIsSingleTerm(true);
            processStaffs(result, attendanceMonthlyReportMap, staffAttendancePeriodReport, inputDataMap, staffReferencedDataMap, staffs, staffPayrollInfoMap, basicStaffInfoMap, basedOnTermDataMap);
        }
        if (CollectionUtils.isNotEmpty(logMap)) {
            //save logger oneshot
            List<CalcLog> allLogs = new ArrayList<>();
            for (Map.Entry<StaffInfoSnapshot, PersonalCalcLog> entry : logMap.entrySet()) {
                PersonalCalcLog personalCalcLog = entry.getValue();
                allLogs.addAll(personalCalcLog.getLogs());
            }
            if (CollectionUtils.isNotEmpty(allLogs)) {
                jobRepository.logInBatch(simpleStepContext, allLogs);
            }
            logMap.clear();
        }

        PreSalaryCalcProcessedResult processedResult = new PreSalaryCalcProcessedResult(result, termId, refServiceStatistics);

        clock.stop();

        logger.info("processed=" + clock.getTotalTimeMillis());

        return processedResult;
    }

    private void processStaffs(List<Pair<StaffPayPeriodTermCalcContext, StaffSalaryCalcPeriod>> list, Map<String, AttendanceMonthlyReport> attendanceMonthlyReportData,
                               Map<String, List<AttendancePeriodReport>> attendancePeriodReport,
                               Map<String, StaffPayPeriodTermSalary> inputData,
                               Map<String, Map<String, Map<String, Object>>> staffReferencedData,
                               List<StaffSalaryCalcPeriod> staffs,
                               Map<String, StaffPayrollInfo> staffPayrollInfoMap,
                               Map<String, SyncedBasicStaffInfo> basicStaffInfoMap,
                               Map<String, StaffSalarySnapshot> basedOnTermDataMap) {
        StopWatch clock = new StopWatch();
        boolean isSinglePhase = CollectionUtils.isEmpty(attendancePeriodReport);
        for (StaffSalaryCalcPeriod staffPeriodSalary : staffs) {

            clock.start("==========StopWatch:" + staffPeriodSalary.getStaffName());
            PersonalCalcLog log = new PersonalCalcLog();

            processSalaryPeriod(staffPeriodSalary);

            String staffId = staffPeriodSalary.getStaffId();
            SyncedBasicStaffInfo syncedBasicStaffInfo = basicStaffInfoMap.get(staffId);
            if (syncedBasicStaffInfo == null) {
                log.addLog(StepExecutionLog.LogLevel.ERROR, "[员工信息]", String.format("员工ID:(%s)基本信息不存在", staffId));
                continue;
            }
            StaffPayrollInfo payrollInfo = staffPayrollInfoMap.get(staffId);
            SimpleStaffPayrollInfo spi = null;

            StaffInfoSnapshot staffInfo = getLogKey(syncedBasicStaffInfo);

            if (payrollInfo == null) {
                spi = new SimpleStaffPayrollInfo(TaxRole.NATIVE.toString(),TaxRole.NATIVE.getTitle(),
                        TaxDivision.defaultTaxDivion().getCode(),TaxDivision.defaultTaxDivion().getName(),
                        WorkHourType.STANDARD.toString(),WorkHourType.STANDARD.getTitle());

                log.addLog(StepExecutionLog.LogLevel.INFO, "[员工信息]", String.format("员工:(%s)未设置纳税地区,采用默认值[中国大陆]", staffInfo.getStaffName()));
                log.addLog(StepExecutionLog.LogLevel.INFO, "[员工信息]", String.format("员工:(%s)未设置纳税人身份,采用默认值[中国大陆]", staffInfo.getStaffName()));
                log.addLog(StepExecutionLog.LogLevel.TRACE, "[员工信息]", String.format("员工:(%s)未设置工时类型,采用默认值[标准工时]", staffInfo.getStaffName()));
            } else {
                staffPeriodSalary.setStaffName(staffInfo.getStaffName());
                if (payrollInfo.getTaxDivision() == null || payrollInfo.getTaxRole() == null || payrollInfo.getWorkHourType() == null) {
                    spi = new SimpleStaffPayrollInfo();
                    if (payrollInfo.getTaxDivision() == null) {
                        log.addLog(StepExecutionLog.LogLevel.INFO, "[员工信息]", String.format("员工:(%s)未设置纳税地区,采用默认值[中国大陆]", staffInfo.getStaffName()));
                        spi.setTaxDivision(TaxDivision.defaultTaxDivion().getCode());
                        spi.setTaxDivisionName(TaxDivision.defaultTaxDivion().getName());
                    } else {
                        spi.setTaxDivision(payrollInfo.getTaxDivision().getCode());
                        spi.setTaxDivision(payrollInfo.getTaxDivision().getName());
                    }
                    if (payrollInfo.getTaxRole() == null) {
                        log.addLog(StepExecutionLog.LogLevel.INFO, "[员工信息]", String.format("员工:(%s)未设置纳税人身份,采用默认值[中国大陆]", staffInfo.getStaffName()));
                        spi.setTaxRole(TaxRole.NATIVE.toString());
                        spi.setTaxRoleName(TaxRole.NATIVE.getTitle());
                    } else {
                        spi.setTaxRole(payrollInfo.getTaxRole().toString());
                        spi.setTaxRoleName(payrollInfo.getTaxRole().getTitle());
                    }
                    if (payrollInfo.getWorkHourType() == null) {
                        log.addLog(StepExecutionLog.LogLevel.TRACE, "[员工信息]", String.format("员工:(%s)未设置工时类型,采用默认值[标准工时]", staffInfo.getStaffName()));
                        spi.setWorkHourType(WorkHourType.STANDARD.toString());
                        spi.setWorkHourType(WorkHourType.STANDARD.getTitle());
                    } else {
                        spi.setWorkHourType(payrollInfo.getWorkHourType().toString());
                        spi.setWorkHourTypeName(payrollInfo.getWorkHourType().getTitle());
                    }
                }else{
                    spi = new SimpleStaffPayrollInfo();
                    spi.setTaxDivision(payrollInfo.getTaxDivision().getCode());
                    spi.setTaxDivisionName(payrollInfo.getTaxDivision().getName());
                    spi.setWorkHourType(payrollInfo.getWorkHourType().toString());
                    spi.setWorkHourTypeName(payrollInfo.getWorkHourType().getTitle());
                    spi.setTaxRole(payrollInfo.getTaxRole().toString());
                    spi.setTaxRoleName(payrollInfo.getTaxRole().getTitle());
                }
            }
            //收入项目部分
            StaffPayPeriodTermSalary inputSalary = inputData.get(staffId);
            //员工的服务引用数据
            Map<String, Map<String, Object>> staffRefServiceData = staffReferencedData.get(staffId);
            //设置考勤数据
            AttendanceMonthlyReport monthlyReport = attendanceMonthlyReportData.get(staffId);
            List<AttendancePeriodReport> periodReports = attendancePeriodReport.get(staffId);


            StaffPayPeriodTermCalcContext staffCalcContext = new StaffPayPeriodTermCalcContext();

            staffCalcContext.setCompanyId(salaryCalcContext.getCompanyId());
            staffCalcContext.setId(new StaffSalaryTermId(salaryCalcContext.getTermId(), staffId));
            staffCalcContext.setStaffName(syncedBasicStaffInfo.getStaffName());
            staffCalcContext.setMobileNo(syncedBasicStaffInfo.getMobileNo());
            staffCalcContext.setDepartmentId(syncedBasicStaffInfo.getDepartmentId());
            staffCalcContext.setDepartmentName(syncedBasicStaffInfo.getDepartmentName());
            staffCalcContext.setIsSinglePhase(isSinglePhase);

            StaffSalaryCalcContext calcContext = new StaffSalaryCalcContext();
            calcContext.setStaffInfo(spi);
            if (inputSalary != null) {
                StaffSalarySnapshot summarizedData = inputSalary.getSummarizedData();
                if (summarizedData != null) {
                    Map<String, DynamicCell> data = summarizedData.getData();
                    if (data != null) {
                        Map<String, DynamicCell> filtedCells = getInputCellMapOnly(inputData, data);
                        calcContext.setInputData(filtedCells);
                    } else {
                        calcContext.setInputData(null);
                    }
                }
            } else {
                calcContext.setInputData(null);
            }

            calcContext.setRefData(staffRefServiceData);
            if (CollectionUtils.isEmpty(staffReferencedData)) {
                staffReferencedData = new HashMap<>(1);
            }


            if (CollectionUtils.isNotEmpty(this.salaryReferences)) {
                for (String serviceId : this.salaryReferences.keySet()) {
                    if (!this.salaryReferences.containsKey(serviceId)) {
                        String serviceName = refCodeAndNameMap.get(serviceId);
                        //&& salaryCalcContext.hasSIHFFields
                        if (serviceId.equals(ServiceProvider.SERVICE_ID.SIHF)) {
                            staffCalcContext.setSihfStatus(StaffRefStatus.NOT_OK);
                            refServiceStatistics.increaseSihfNotOkCount();
                            log.addLog(StepExecutionLog.LogLevel.WARNING, "[数据引用]", String.format("无法获取员工:(%s) %s 数据", staffInfo.getStaffName(), serviceName));
                        }
                    } else {
                        //&& salaryCalcContext.hasSIHFFields
                        if (serviceId.equals(ServiceProvider.SERVICE_ID.SIHF)) {
                            staffCalcContext.setSihfStatus(StaffRefStatus.OK);
                        }
                    }
                }
            }
            if (monthlyReport != null && salaryCalcContext.hasAttendanceFields) {
                staffCalcContext.setAttendanceStatus(StaffRefStatus.OK);
            } else if (salaryCalcContext.hasAttendanceFields) {
                staffCalcContext.setAttendanceStatus(StaffRefStatus.NOT_OK);
                refServiceStatistics.increaseAttendanceNotOkCount();
                log.addLog(StepExecutionLog.LogLevel.WARNING, "[数据引用]", String.format("无法获取员工:(%s) %s 数据", staffInfo.getStaffName(), "考勤月报"));

                logger.info(String.format("---无法获取员工:(%s) %s 数据", staffInfo.getStaffName(), "考勤月报"));
            }

            if (staffPeriodSalary != null) {
                staffPeriodSalary.setCompanyId(null);
                staffPeriodSalary.setStaffId(null);
                staffPeriodSalary.setStaffName(null);
            }

            calcContext.setSpData(staffPeriodSalary);
            if (periodReports != null) {
                periodReports.stream().forEach(p -> {
                    p.setStaffId(null);
                });
            }
            AttendancePeriodReport report = reportConvertor.convertDtoToEntity(monthlyReport);
            if (report != null) {
                report.setStaffId(null);
                report.setStaffName(null);
            }
            calcContext.setAtm(report);
            calcContext.setAtps(periodReports);

            StaffSalarySnapshot slimSnapshot = new StaffSalarySnapshot();
            Map<String, DynamicCell> slimData = null;
            if (CollectionUtils.isNotEmpty(basedOnTermDataMap)) {
                StaffSalarySnapshot baseOnsalarySnapshot = basedOnTermDataMap.get(staffId);
                if (baseOnsalarySnapshot != null) {
                    Map<String, DynamicCell> data = baseOnsalarySnapshot.getData();
                    if (data != null) {
                        slimData = new HashMap<>(termRefFields.size());
                        for (String ref : this.termRefFields) {
                            slimData.put(ref, data.get(ref));
                        }
                        slimSnapshot.setData(slimData);
                    }
                }
            }
            calcContext.setBasedOnTermSalary(slimSnapshot);
            staffCalcContext.setContext(calcContext);

            StaffSalaryCalcPeriod savedCalcSalary = salaryTermPayConvertor.convert(staffCalcContext);

            savedCalcSalary.setTermId(salaryCalcContext.getTermId());
            savedCalcSalary.setCompanyId(salaryCalcContext.getCompanyId());
            savedCalcSalary.setStartOn(payrollPeriodRange.getStart());
            savedCalcSalary.setEndOn(payrollPeriodRange.getEnd());
            savedCalcSalary.setStaffId(staffId);
            savedCalcSalary.setStaffName(syncedBasicStaffInfo.getStaffName());
            savedCalcSalary.setMobileNo(syncedBasicStaffInfo.getMobileNo());
            savedCalcSalary.setDepartmentId(syncedBasicStaffInfo.getDepartmentId());
            savedCalcSalary.setDepartmentName(syncedBasicStaffInfo.getDepartmentName());
            savedCalcSalary.setYear(salaryCalcContext.getPayPeriodYear());
            savedCalcSalary.setMonth(salaryCalcContext.getPayPeriodMonth());

            list.add(Pair.of(staffCalcContext, savedCalcSalary));
            clock.stop();
            if (CollectionUtils.isNotEmpty(log.getLogs())) {
                addLogToLogMap(staffInfo, log);
            }

        }
    }

    /**
     * 处理薪资的时间边界
     *
     * @param salaryCalcPeriod
     */
    private void processSalaryPeriod(StaffSalaryCalcPeriod salaryCalcPeriod) {
        List<StaffSalaryCalcPeriodTerm> phases = salaryCalcPeriod.getPhases();
        if (CollectionUtils.isNotEmpty(phases)) {
            phases.stream().forEach(p -> {
                if (p.getStartOn().before(this.unionRange.getStart())) {
                    p.setStartOn(this.unionRange.getStart());
                }
                if (p.getEndOn() == null || p.getEndOn().after(this.unionRange.getEnd())) {
                    p.setEndOn(this.unionRange.getEnd());
                }
            });
        }
    }

    private Map<String, DynamicCell> getInputCellMapOnly(Map<String, StaffPayPeriodTermSalary> inputData, Map<String, DynamicCell> data) {
        Map<String, DynamicCell> filtedCells = null;
        for (Map.Entry<String, DynamicCell> entry : data.entrySet()) {
            String code = entry.getKey();
            if (inputSalaryFieldSet.contains(code)) {
                if (filtedCells == null) {
                    filtedCells = new HashMap<>(inputData.size());
                }
                filtedCells.put(code, entry.getValue());
            }
        }
        return filtedCells;
    }


    private void addLogToLogMap(StaffInfoSnapshot staffInfo, PersonalCalcLog log) {
        PersonalCalcLog calcLog = logMap.get(staffInfo);
        if (calcLog == null) {
            logMap.put(staffInfo, log);
        } else {
            calcLog.addLogs(log.getLogs());
            logMap.put(staffInfo, calcLog);
        }
    }

    private StaffInfoSnapshot getLogKey(SyncedBasicStaffInfo staffPeriodSalary) {
        StaffInfoSnapshot staffInfo = new StaffInfoSnapshot();
        staffInfo.setStaffId(staffPeriodSalary.getId());
        staffInfo.setStaffName(staffPeriodSalary.getStaffName());
        return staffInfo;
    }


    @BeforeProcess
    public void setSimpleStepContext(Map<String, StaffSalaryCalcPeriod> item, SimpleStepContext simpleStepContext) {
        this.simpleStepContext = simpleStepContext;
    }
}